package;
import flash.display.MovieClip;
import flash.display.DisplayObjectContainer;
import flash.display.Sprite;
import flash.events.AsyncErrorEvent;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.SyncEvent;
import flash.events.NetStatusEvent;
import flash.net.NetConnection;
import flash.net.SharedObject;
import flash.net.Responder;
import flash.text.TextField;
import flash.Lib;
import fl.transitions.Tween;
import fl.transitions.TweenEvent;
import fl.transitions.easing.Regular;
import fl.transitions.easing.Bounce;
using Reflect;

class Game extends Panel
{
	static var colWidth = 65;
	static var rowHeight = 52;
	static var offset = 25;
	static var bottomOffset = 38;
	static var tweenx:Tween;
	static var tweeny:Tween;
	
	var container:MovieClip;
	var background:Sprite;
	var yField:TextField;
	var rField:TextField;
	var grid:Grid;
	var foreground:MovieClip;
	
	var chips:Hash<Chip>;
	var parents:Hash<DisplayObjectContainer>;
	var columns:Array<Array<Chip>>;
	var activeChip:Chip;
	var lastX:Int;
	var lastY:Int;
	var gameover:Bool;
	var history:Array<Dynamic>;
	var historyIndex:Int;
	
	var so:SharedObject;
	var connection:NetConnection;
	var console:Console;
	var userName:String;
	var gameId:Float;
	var color:String;
	var lobby:Lobby;
	override public function new(gameId, userName, console, lobby)
	{
		super();
		this.gameId = gameId;
		this.userName = userName;
		this.console = console;
		this.lobby = lobby;

		columns = new Array();
		for(i in 0...7)
		{
			columns[i] = new Array();
		}
		chips = new Hash();
		parents = new Hash();
		
		container = createView();
		addChild(container);
		minWidth=container.width+20;
		minHeight=container.height+100;
		
		connection = new NetConnection();
		connection.objectEncoding = 0;
		connection.client=this;
		connection.addEventListener(NetStatusEvent.NET_STATUS, onNetStatus);
		connection.addEventListener(AsyncErrorEvent.ASYNC_ERROR, onConnectionError);
		connection.connect(Main.path);
		
		//setGameId(gameId);
		addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
	}
	function onNetStatus(event:NetStatusEvent):Void
	{
		//console.log(event);
		if(event.info.code =='NetConnection.Connect.Success')
		{
			setGameId(gameId);
		}
	}
	function setGameId(id:Float)
	{
		//console.log("game ?id="+id);
		this.setTitle("Game : " + id);
		so = SharedObject.getRemote("so" + id, connection.uri, true);
		
		so.connect(connection);
		so.client = this; 
		//so.addEventListener(NetStatusEvent.NET_STATUS, onSharedObjectStatus);
		so.addEventListener(AsyncErrorEvent.ASYNC_ERROR, onSharedObjectError);
		so.addEventListener(SyncEvent.SYNC, onSharedObjectSync);
		
	}
	function onAddedToStage(event:Event):Void
	{
		removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
		stage.addEventListener(Event.RESIZE, onStageResize);
		onStageResize(null);
		onResize(null);
	}
	function onStageResize(event:Event):Void
	{
		if(visible && stage!=null)
		{
			x = (stage.stageWidth - width)/2 + 100;
			y = (stage.stageHeight - height)/2;
		}
	}
	override function onResize(event:MouseEvent):Void
	{
		super.onResize(event);//setSize(stage.mouseX-this.x+15, stage.mouseY-this.y+15);
		container.x = (w - container.width)/2 + 75 + Chip.chipSize;
		container.y = (h - container.height)/2;
	}
	function createView()
	{
		var container = new MovieClip();
		
		background = new Sprite();
		background.name = "b";
		parents.set(background.name, background);
		container.addChild(background);
		
		grid = new Grid();
		container.addChild(grid);

		foreground = new MovieClip();
		foreground.name = "f";
		container.addChild(foreground);
		parents.set(foreground.name, foreground);
		
		yField = new TextField();
		yField.height = 20;
		yField.x = grid.x + grid.width + 50;
		yField.y = grid.y + grid.height +50;
		container.addChild(yField);
		
		rField = new TextField();
		rField.height = 20;
		rField.x = grid.x - 50 - Chip.chipSize;
		rField.y = grid.y + grid.height +50;
		container.addChild(rField);
		
		setupChips("y");
		setupChips("r");

		return container;
	}
	
	function onConnectionError(event:AsyncErrorEvent):Void
	{
		console.log(event);
	}
	function onSharedObjectError(event:AsyncErrorEvent):Void
	{
		console.log(event);
	}
	function onSharedObjectStatus(event:NetStatusEvent):Void
	{
		//console.log(event);
	}
	function onSharedObjectSync(event:SyncEvent)
	{
		//console.log(event);
		var list:Array<Dynamic> = event.changeList;
		for(obj in list)
		{
			switch(obj.code)
			{
				case "clear":
						onFirstSync(event);
						if(so.data.gameover)
						{
							yField.text = so.data.y;
							rField.text = so.data.r;
							return;
						}
				
				
				case "success":
					switch(obj.name)
					{
						case "gameover":
							connection.close();
							so.close();
					}
				
				case "change":
					switch(obj.name)
					{
						case "xy":
							var coords:Array<Dynamic> = so.data.xy;
							if(coords!=null)
							{
								var chip = chips.get(coords[0]);
								
								if(chip.parent != parents.get(coords[3]))
									parents.get(coords[3]).addChild(chip);
								chip.x = coords[1];
								chip.y = coords[2];
								if(coords[4] != -1 && columns[coords[4]][columns[coords[4]].length-1]!=chip)
								{
									columns[coords[4]].push(chip);
								}
							}
							
						case "y":
							setTitle(so.data.y +" vs " + so.data.r +  " (game:" + gameId+")");
							yField.text = so.data.y;
							
						case "r":
							setTitle(so.data.y +" vs " + so.data.r +  " (game:" + gameId+")");
							rField.text = so.data.r;
							
						case "p":
							yField.border=false;
							rField.border=false;
							this.field(so.data.p+"Field").border=true;
						
						case "gameover":
							this.gameover = true;
							disableChips("y");
							disableChips("r");
							console.log("gameover: " + so.data.winner + " wins!");
							connection.close();
							so.close();
					}
			}
		}
	}
	function onFirstSync(event:SyncEvent):Void
	{
		//console.log(event);
		if(so.data.y == null)// || so.data.y == userName && !so.data.gameover)
		{
			this.color="y";
			so.setProperty("y", userName);
			disableChips("r");
			so.setProperty("p", "y");
			so.setProperty("moves", new Array());
			yField.text = userName;
			setTitle(userName +" vs ???  (game:" + gameId+")");
		}
		else if(so.data.r == null)// || so.data.r == userName && !so.data.gameover)
		{
			this.color="r";
			so.setProperty("r", userName);
			disableChips("y");
			rField.text = userName;
			yField.text = so.data.y;
			setTitle(so.data.y +" vs " + userName + " (game:" + gameId+")");
			connection.call("setGameOponent", new Responder(onResultSetGameOponent, onErrorSetGameOponent), gameId, userName);
		}
		else
		{
			this.color="";
			disableChips("r");
			disableChips("y");
			var moves:Array<Array<Dynamic>> = so.data.moves;
			var movesCopy:Array<Array<Dynamic>> = new Array();
			for(m in moves)
			{
				if(m!=null)
				{
					var move:Array<Dynamic> = m;
					var moveCopy:Array<Dynamic> = new Array();
					for(i in move)
						moveCopy.push(i);
					movesCopy.push(moveCopy);
				}
			}
			if(so.data.gameover)
			{
				so.close();
				connection.close();
				so.removeEventListener(SyncEvent.SYNC, onSharedObjectSync);
			}
			playHistory(movesCopy, so.data.gameover);
			setTitle(so.data.y +" vs " + so.data.r +  " (game:" + gameId+")");
		}
	}
	function onResultSetGameOponent(obj)
	{
		console.log(obj);
	}
	function onErrorSetGameOponent(obj)
	{
		console.log(obj);
	}
	function playHistory(moves:Array<Array<Dynamic>>, gameover:Bool)
	{
		history = moves;
		historyIndex = 0;
		if(gameover)
			this.addEventListener(Event.ENTER_FRAME, onHistoryEnterFrame);
		else
			for(i in moves)
					playHistoryMove(i);
	}
	function playHistoryMove(coords:Array<Dynamic>)
	{
		if (coords==null) return;
		var chip = chips.get(coords[0]);
		if(chip.parent != parents.get(coords[3]))
			parents.get(coords[3]).addChild(chip);
		chip.x = coords[1];
		chip.y = coords[2];
		if(coords[4] != -1 && columns[coords[4]][columns[coords[4]].length-1]!=chip)
		{
			columns[coords[4]].push(chip);
		}
	}
	function onHistoryEnterFrame(event:Event):Void
	{
		if(historyIndex < history.length-1)
		{
			playHistoryMove(history[historyIndex++]);
		}
		else
		{
			this.removeEventListener(Event.ENTER_FRAME, onHistoryEnterFrame);
			console.log("gameover: " + so.data.winner + " wins!");
		}
	}
	function onMouseDown(event:MouseEvent):Void
	{
		var chip:Chip = event.currentTarget;
		if(chip.color == so.data.p)
		{
			chip.columnIndex = -1;
			stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
			stage.addEventListener(MouseEvent.MOUSE_MOVE, onChipMove);
			chip.startDrag();
			activeChip = chip;
			lastX = Math.round(activeChip.x);
			lastY = Math.round(activeChip.x);
		}
	}
	function onChipMove(event:Event):Void
	{
		var newX = Math.round(activeChip.x);
		var newY = Math.round(activeChip.y);
		if(newX != lastX || newY != lastY)
		{
			lastX = newX;   lastY = newY;
			so.setProperty("xy", [activeChip.name, newX, newY, activeChip.parent.name, activeChip.columnIndex]);
		}
	}
	function onMouseUp(event:MouseEvent):Void
	{
		stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
		stage.removeEventListener(MouseEvent.MOUSE_MOVE, onChipMove);
		activeChip.stopDrag();
		drop(activeChip);
	}
	function drop(chip:Chip)
	{
		var startX = chip.x;
		var startY = chip.y;
		var endX = chip.originX;
		var endY = chip.originY;
		var columnIndex = getChoosenColumn(chip);
		if (columnIndex != -1 && columns[columnIndex].length < 6 && chip.y < grid.y - chip.height+15)// && chip.x > grid.x && chip.x < grid.x+grid.width )
		{
			endX = offset + columnIndex * colWidth;
			endY = grid.y + grid.height-bottomOffset - chip.height  - rowHeight * columns[columnIndex].length;
			columns[columnIndex].push(chip);
			chip.columnIndex = columnIndex;
			if(checkFourness())
			{
				disableChips(color);
				gameover=true;
			}
		}
		if(endX != chip.originX)
		{
			chip.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
			chip.buttonMode = false;
			background.addChild(chip);
			so.setProperty("xy", [chip.name, chip.x, chip.y, chip.parent.name, chip.columnIndex]);
			var temp = so.data.moves;
			temp.push([chip.name, chip.x, chip.y, chip.parent.name, chip.columnIndex]);
			so.setProperty("moves",temp);
			so.setProperty("p", so.data.p=="y"?"r":"y");
			if(color=="y")
			{
				yField.border=false;
				rField.border=true;
			}
			else
			{
				yField.border=true;
				rField.border=false;
			}
		}
		tweenx = new Tween(chip,"x", Regular.easeIn, startX, endX, 0.2,true);
		tweeny = new Tween(chip,"y", Bounce.easeOut, startY, endY, 0.5, true);
		tweenx.addEventListener("motionChange", onChipMove);
		tweeny.addEventListener("motionChange", onChipMove);
		if(gameover)
		{
			tweenx.addEventListener("motionFinish", onMotionFinishX);
			tweeny.addEventListener("motionFinish", onMotionFinishY);
		}
	}
	var xDone:Bool;
	var yDone:Bool;
	function onMotionFinishX(event)
	{
		tweenx.removeEventListener("motionFinish", onMotionFinishX);
		xDone=true;
		if(yDone)
		{
			console.log("gameover: " + userName + " wins!");
			so.setProperty("winner", userName);
			so.setProperty("gameover", true);
		}		
	}
	function onMotionFinishY(event)
	{
		tweeny.removeEventListener("motionFinish", onMotionFinishY);
		yDone=true;
		if(xDone)
		{
			console.log("gameover: " + userName + " wins!");
			so.setProperty("winner", userName);
			so.setProperty("gameover", true);
		}
	}
	
	function checkFourness():Bool
	{
		for(array in getArrays())
			if(array.length >= 4)
				for(i in 0...array.length-3)
					if(array[i]!=null && array[i+1]!=null && array[i+2]!=null && array[i+3]!=null)
						if(array[i].color == array[i+1].color && array[i].color == array[i+2].color && array[i].color == array[i+3].color)
							return true;
		return false;
	}
	function getArrays():Array<Array<Chip>>
	{
		var out = new Array();
		//columns:
		for(i in 0...7)
			out.push(columns[i]);
		//rows:
		for(j in 0...6)
		{
			var arr = new Array();
			for(i in 0...7)
				arr.push(columns[i][j]);
			out.push(arr);
		}
		//diagonals:
		out.push([columns[0][2], columns[1][3], columns[2][4], columns[3][5]]);
		out.push([columns[0][1], columns[1][2], columns[2][3], columns[3][4], columns[3][4]]);
		out.push([columns[0][0], columns[1][1], columns[2][2], columns[3][3], columns[4][4], columns[5][5]]);
		out.push([columns[1][0], columns[2][1], columns[3][2], columns[4][3], columns[5][4], columns[6][5]]);
		out.push([columns[2][0], columns[3][1], columns[4][2], columns[5][3], columns[6][4]]);
		out.push([columns[3][0], columns[4][1], columns[5][2], columns[6][3]]);
		return out;
	}
	function getChoosenColumn(chip:Chip):Int
	{
		for(i in 0...7)
		{
			var leftBound  = offset +   i   * colWidth - chip.width/2;
			var rightBound = offset + (i+1) * colWidth - chip.width/2;
			if(chip.x > leftBound && chip.x < rightBound)
				return i;
		}
		return -1;
	}

	function setupChips(color:String)
	{
		for(i in 0...21)
		{
			var chip = new Chip(color, i);
			chip.buttonMode=true;
			chip.originX = chip.x = color=="y"?grid.x + grid.width + 50:grid.x - 50 - chip.width;
			chip.originY = chip.y = grid.y + grid.height +50 - chip.height - i * 10;
			chip.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
			foreground.addChild(chip);
			chips.set(chip.name, chip);
		}
	}
	function disableChips(color:String)
	{
		for(i in 0...21)
		{
			var chip:Chip = chips.get(color+i);
			chip.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
			chip.buttonMode=false;
		}
	}
	override function onClose(event:MouseEvent):Void
	{
		//if(so.data.r == null)
		{
			super.onClose(event);
			so.close();
			connection.close();
		}
	}
}
class Chip extends MovieClip
{
	public static var chipSize:Float=-1;
	private var disc:MovieClip;
	public var color:String;
	public var columnIndex:Int;
	public function new(color:String, id:Int)
	{
		super();
		this.color = color;
		if(color=="y")
		{
			disc = new Yellow();
			this.name = "y"+id;
		}
		else if(color=="r")
		{
			disc = new Red();
			this.name = "r"+id;
		}
		if(chipSize==-1)
		{
			chipSize = disc.width;
		}
		this.addChild(disc);
	}
}